package com.cysoft.starter.log.core.aspect;

import com.cysoft.starter.log.core.model.LogInfo;
import com.cysoft.starter.log.core.properties.OperateLogProperties;
import com.cysoft.starter.log.core.repository.DataRepository;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.CompletableFuture;

import static com.alibaba.fastjson.JSON.toJSONString;

/**
 * @author 玖零
 * @version v0.0.1
 * @project cysoft-spring-boot-starter-log
 * @package com.cysoft.starter.log.core.aspect
 * @date 2025-02-04 12:06
 * @desc 日志切面
 */
@Slf4j
@Aspect
public class LogAspect extends BaseAspect{


    /**
     * 日志切面构造函数
     * @param dataRepository          数据仓储
     * @param operateLogProperties    日志配置
     */
    public LogAspect(DataRepository dataRepository, OperateLogProperties operateLogProperties) {
        super(dataRepository,operateLogProperties);
    }

    /**
     * 请求方式
     */
    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping) ||" +
            "@annotation(org.springframework.web.bind.annotation.PostMapping) ||" +
            "@annotation(org.springframework.web.bind.annotation.PutMapping) ||" +
            "@annotation(org.springframework.web.bind.annotation.DeleteMapping) ||" +
            "@annotation(org.springframework.web.bind.annotation.PatchMapping) ||" +
            "@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void allAnnotationMethod() {
    }


    /**
     * 环绕通知监控日志
     * @param point
     * @return
     */
    @Around("allAnnotationMethod()")
    @SneakyThrows
    public Object around(ProceedingJoinPoint point) {
        long startTime = System.currentTimeMillis();
        ServletRequestAttributes requestAttributes = getServletRequestAttributes();
        if (requestAttributes == null) {
            log.warn("操作日志该方法不是web接口请求");
            return proceedWithExceptionHandling(point);
        }
        HttpServletRequest request = requestAttributes.getRequest();
        if (super.checkIgnore(request, point)) {
            log.info("过滤范围内的请求无需记录日志信息");
            return proceedWithExceptionHandling(point);
        }
        //拼接日志信息
        LogInfo logInfo = super.assemblyRecord(request,point);
        try {
            return executeMethod(point, logInfo);
        } catch (Throwable throwable) {
            handleException(logInfo, throwable);
        } finally {
            long endTime = System.currentTimeMillis();
            logInfo.setDuration(endTime - startTime);
            log.info("日志处理耗时: {} 毫秒", endTime - startTime);
            //异步保存
            CompletableFuture.runAsync(() -> output(logInfo));
        }
        return null;
    }


    /**
     * 执行目标方法并处理结果和异常
     *
     * @param point   连接点
     * @param logInfo 日志信息对象
     * @return 方法执行结果
     */
    private Object executeMethod(ProceedingJoinPoint point, LogInfo logInfo) throws Throwable {
        Object result = point.proceed();
        logInfo.setRequestResult(true);
        logInfo.setResponseBody(Objects.isNull(result) ? null : toJSONString(result));
        return result;
    }

    /**
     * 处理异常信息
     *
     * @param logInfo   日志信息对象
     * @param throwable 异常对象
     */
    private void handleException(LogInfo logInfo, Throwable throwable) {
        StackTraceElement[] stackTraceElements = throwable.getStackTrace();
        StackTraceElement traceElement = stackTraceElements[0];
        String errorMessage = String.format("%s\n%s", traceElement, throwable);
        logInfo.setRequestResult(false);
        logInfo.setErrorMessage(errorMessage);
    }



    /**
     * 获取ServletRequestAttributes
     * @return ServletRequestAttributes对象
     */
    private ServletRequestAttributes getServletRequestAttributes() {
        return (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    }

    /**
     *  封装异常处理逻辑
     * @param point
     * @return
     */
    private Object proceedWithExceptionHandling(ProceedingJoinPoint point) {
        try {
            return point.proceed();
        } catch (Throwable throwable) {
            log.error("执行方法时发生异常", throwable);
            return null;
        }
    }
}
